package com.example.deepschool.Model.Response;

import lombok.SneakyThrows;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourceRegion;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.util.UriUtils;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * ApiResponse用于构建API的响应体，它包含响应的状态码、主体、头信息等。
 * 通过这个类，可以方便地组装一个符合RESTful API标准的响应体。
 *
 * @param <T> 响应体的数据类型，支持泛型以便于处理不同类型的数据。
 */
public class ApiResponse<T> {
	
	/**
	 * 存储响应头信息的映射表。
	 */
	private final Map<String, String> headers;
	private int status;
	private T body;
	private ResponseData responseData;
	
	/**
	 * 构造函数初始化headers映射表。
	 */
	public ApiResponse () {
		this.headers = new HashMap<>();
	}
	
	/**
	 * 提供一个静态方法创建一个空的ApiResponse对象，用于后续组装。
	 *
	 * @return ApiResponse的空实例。
	 */
	public static ApiResponse<Object> GetApiResponse () {
		return new ApiResponse<>();
	}
	
	/**
	 * 设置响应状态码为200 OK。
	 *
	 * @return ApiResponse实例，支持链式调用。
	 */
	public ApiResponse<T> ok () {
		this.status = 200;
		return this;
	}
	
	/**
	 * 设置响应的状态码。
	 *
	 * @param status HTTP响应状态码。
	 * @return ApiResponse实例，支持链式调用。
	 */
	public ApiResponse<T> status (int status) {
		this.status = status;
		return this;
	}
	
	/**
	 * 设置响应的主体。
	 *
	 * @param body 响应的主体数据。
	 * @return ApiResponse实例，支持链式调用。
	 */
	public ApiResponse<T> body (T body) {
		this.body = body;
		return this;
	}
	
	/**
	 * 设置二进制响应体。
	 *
	 * @param binaryBody 二进制响应体。
	 * @return ApiResponse实例，支持链式调用。
	 */
	public ApiResponse<T> binBody (ResponseData binaryBody) {
		this.responseData = binaryBody;
		return this;
	}
	
	/**
	 * 添加一个响应头。
	 *
	 * @param key   头信息的键。
	 * @param value 头信息的值。
	 * @return ApiResponse实例，支持链式调用。
	 */
	public ApiResponse<T> addHeader (String key, String value) {
		this.headers.put(key, value);
		return this;
	}
	
	/**
	 * 批量添加响应头。
	 *
	 * @param headers 包含头信息的映射表。
	 * @return ApiResponse实例，支持链式调用。
	 */
	public ApiResponse<T> AddHeaders (Map<String, String> headers) {
		this.headers.putAll(headers);
		return this;
	}
	
	/**
	 * 根据当前设置构建一个ResponseEntity对象，用于HTTP响应。
	 *
	 * @return 构建好的ResponseEntity对象。
	 */
	public ResponseEntity<T> build () {
		HttpHeaders headers = new HttpHeaders();
		for (String key : this.headers.keySet()) {
			headers.add(key, this.headers.get(key));
		}
		return ResponseEntity.status(this.status).headers(headers).body(this.body);
	}
	
	/**
	 * 生成支持范围请求的在线构建文件分段下载响应。
	 * <p>
	 * 方法分析请求中的Range头部，以确定文件传输的起始字节位置。它计算分段的结束位置，
	 * 保证不超过文件总大小或预设的最大块大小。通过创建`ResourceRegion`实例来封装文件部分，
	 * 并设置响应头以允许浏览器或其他客户端进行分段下载或流式播放，包括正确的Content-Type和Content-Disposition。
	 *
	 * @return 响应实体，包含指定范围的文件资源区域及相应的HTTP头部，状态码为206（Partial Content）。
	 */
	@SneakyThrows
	public ResponseEntity<ResourceRegion> onlineBuild () {
		// 获取请求范围和文件路径
		String range = this.responseData.getRange();
		String path = this.responseData.getPath();
		
		// 准备文件和其大小信息
		File file = new File(path);
		long fileSize = file.length();
		long start = 0; // 初始化起始读取位置
		
		// 处理解析Range头信息，设定起始读取位置
		if (range != null && range.startsWith("bytes=")) {
			String[] rangeBounds = range.substring("bytes=".length()).split("-");
			start = Long.parseLong(rangeBounds[0]);
		}
		
		// 计算分段结束位置，确保不超出文件大小或最大允许块大小
		long end = Math.min(fileSize, start + this.responseData.getMaxChunkSize());
		
		// 创建文件资源和资源区域对象
		Resource fileResource = new FileSystemResource(path);
		ResourceRegion resourceRegion = new ResourceRegion(fileResource, start, end - start);
		
		// 设置响应头，包括自定义头、内容类型和内容展示方式
		HttpHeaders headers = new HttpHeaders();
		for (Map.Entry<String, String> headerEntry : this.headers.entrySet()) {
			headers.add(headerEntry.getKey(), headerEntry.getValue());
		}
		headers.add(HttpHeaders.CONTENT_TYPE, this.responseData.getType());
		headers.add(HttpHeaders.CONTENT_DISPOSITION, "inline"); // 适合内联查看如图片、文档等
		
		// 构造并返回响应实体，状态码206表明是部分内容响应
		return ResponseEntity.status(206).headers(headers).body(resourceRegion);
	}
	
	
	/**
	 * 处理文件下载请求，支持范围下载。
	 * <p>
	 * 根据请求中的Range头来决定是否进行范围下载。若提供了Range头，则构建支持断点续传的响应；
	 * 若没有Range头，则执行普通下载。此外，自动处理文件名的UTF-8编码，确保跨平台兼容性。
	 *
	 * @return 返回一个包含文件资源区域的`ResponseEntity`，根据情况设置状态码为200（完整内容）或206（部分内容）。
	 */
	@SneakyThrows
	public ResponseEntity<ResourceRegion> downloadBuild () {
		String range = this.responseData.getRange();
		String path = this.responseData.getPath();
		File file = new File(path);
		long fileSize = file.length();
		String utf8EncodedName = UriUtils.encode(responseData.getName(), StandardCharsets.UTF_8); // 文件名转UTF-8编码
		
		HttpHeaders headers = new HttpHeaders();
		
		if (range != null && range.startsWith("bytes=")) {
			// 处理范围请求
			String[] ranges = range.substring("bytes=".length()).split("-");
			long start = Long.parseLong(ranges[0]);
			long end = (ranges.length > 1) ? Math.min(Long.parseLong(ranges[1]), fileSize - 1) : fileSize - 1;
			long contentLength = end - start + 1;
			
			headers.set(HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
			headers.set(HttpHeaders.CONTENT_RANGE, String.format("bytes %s-%s/%s", start, end, fileSize));
			headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
			headers.set(HttpHeaders.ACCEPT_RANGES, "bytes");
			headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=utf-8''" + utf8EncodedName);
			
			System.out.println("有Range: " + start);
			
			Resource fileResource = new FileSystemResource(path);
			ResourceRegion resourceRegion = new ResourceRegion(fileResource, start, contentLength);
			
			return ResponseEntity.status(206).headers(headers).body(resourceRegion);
		} else {
			// 无范围请求的常规下载处理
			headers.set(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileSize));
			headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=utf-8''" + utf8EncodedName);
			headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
			
			System.out.println("无Range: 0");
			
			Resource fileResource = new FileSystemResource(path);
			ResourceRegion resourceRegion = new ResourceRegion(fileResource, 0, fileSize);
			
			return ResponseEntity.ok().headers(headers).body(resourceRegion);
		}
	}

}